package com.atguigu.tingshu.album.service.impl;

import com.atguigu.tingshu.album.mapper.*;
import com.atguigu.tingshu.album.service.AlbumAttrAndValueService;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.vo.album.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

	@Autowired
	private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumAttrAndValueService albumAttrAndValueService;

    @Autowired
    private TrackInfoMapper trackInfoMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;

    @Autowired
    private BaseCategory3Mapper baseCategory3Mapper;

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 初始化专辑统计数据
     * 此方法用于为指定专辑创建初始统计数据记录，包括播放、订阅、浏览和评论四种统计类型
     * 每种类型初始统计数值为0，通过数据库插入操作完成初始化
     *
     * @param albumId 专辑的唯一标识符
     */
    @Transactional
    public Boolean initAlbumStat(Long albumId) {

        try {
            // 创建一个列表，用于存储专辑的统计类型
            ArrayList<String> albumStatTypes = new ArrayList<>();
            // 添加专辑统计类型：播放
            albumStatTypes.add(SystemConstant.ALBUM_STAT_PLAY);
            // 添加专辑统计类型：订阅
            albumStatTypes.add(SystemConstant.ALBUM_STAT_SUBSCRIBE);
            // 添加专辑统计类型：浏览
            albumStatTypes.add(SystemConstant.ALBUM_STAT_BROWSE);
            // 添加专辑统计类型：评论
            albumStatTypes.add(SystemConstant.ALBUM_STAT_COMMENT);

            // 遍历统计类型列表，为每种类型创建并插入初始统计记录
            for (String albumStatType : albumStatTypes) {
                // 创建一个新的专辑统计对象
                AlbumStat albumStat = new AlbumStat();
                // 设置专辑ID
                albumStat.setAlbumId(albumId);
                // 设置统计类型
                albumStat.setStatType(albumStatType);
                // 设置初始统计数值为0
                albumStat.setStatNum(0);
                // 执行数据库插入操作，初始化统计记录
                albumStatMapper.insert(albumStat);
            }
            return true;
        } catch (Exception e){
            return false;
        }

    }


    /**
     * 保存专辑信息
     *
     * 此方法使用了事务注解，确保在出现异常时能够回滚事务
     * 这是因为Spring的事务管理器只能自动感知运行时异常或错误，从而决定是否回滚事务
     *
     * @param albumInfoVo 专辑信息的视图对象，包含要保存的专辑信息
     */
    @Transactional(rollbackFor = Exception.class)  // spring事务只能感知到运行时异常或者error
    @Override
    public Long saveAlbumInfo(AlbumInfoVo albumInfoVo) {

        // 获取当前AOP代理对象，用于在目标类内部调用自身的方法时触发AOP代理
        AlbumInfoServiceImpl proxy = (AlbumInfoServiceImpl) AopContext.currentProxy();

        // 创建一个新的专辑信息对象
        AlbumInfo albumInfo = new AlbumInfo();

        // 将视图对象中的属性复制到专辑信息对象中
        BeanUtils.copyProperties(albumInfoVo, albumInfo);

        // 获取当前用户的ID
        Long userId = AuthContextHolder.getUserId();

        // 设置专辑信息的用户ID和状态
        albumInfo.setUserId(userId);
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);

        // 除了专辑免费的类型之外 其它两种类型设置免费级数
        if (!SystemConstant.ALBUM_PAY_TYPE_FREE.equals(albumInfoVo.getPayType())) {
            albumInfo.setTracksForFree(5);
        }

        // 向数据库插入专辑信息
        albumInfoMapper.insert(albumInfo);

        // 获取专辑属性值的列表
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        // 如果列表不为空，则遍历列表，为每个属性值创建并插入数据库
        if (!CollectionUtils.isEmpty(albumAttributeValueVoList)) {
            for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
                AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
                albumAttributeValue.setAlbumId(albumInfo.getId());
                albumAttributeValue.setAttributeId(albumAttributeValueVo.getAttributeId());
                albumAttributeValue.setValueId(albumAttributeValueVo.getValueId());
                // 向数据库插入专辑属性值
                albumAttributeValueMapper.insert(albumAttributeValue);
            }
        }

        // 使用代理对象初始化专辑的统计信息
        if (proxy.initAlbumStat(albumInfo.getId())){
            return albumInfo.getId();
        }
        return 0L;

    }


    /**
     * 根据用户查询条件查找用户相册分页信息
     *
     * @param albumInfoVoPage 分页对象，用于存储相册列表的分页信息和数据
     * @param albumInfoQuery 查询对象，包含用户相册的查询条件
     * @return 返回填充了相册数据的分页对象
     */
    @Override
    public IPage<AlbumListVo> findUserAlbumPage(
            IPage<AlbumListVo> albumInfoVoPage, AlbumInfoQuery albumInfoQuery) {
        return albumInfoMapper.findUserAlbumPage(albumInfoVoPage, albumInfoQuery);
    }


    /**
     * 根据专辑ID获取专辑信息
     * 此方法首先通过专辑ID从数据库中查询专辑信息如果找不到对应的专辑信息，
     * 则抛出一个异常表示专辑不存在如果专辑信息存在，则进一步查询与该专辑相关的所有属性值，
     * 并将其设置到专辑信息对象中最后返回完整的专辑信息对象
     *
     * @param albumId 专辑ID，用于唯一标识一个专辑
     * @return AlbumInfo 专辑信息对象，包含专辑详情和相关属性值
     * @throws GuiguException 当专辑不存在时抛出的异常，异常码为201，消息为"专辑不存在"
     */
    @Override
    public AlbumInfo getAlbumInfo(Long albumId) {
        // 查询专辑信息
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        // 检查专辑是否存在
        if (null == albumInfo) {
            throw new GuiguException(201, "专辑不存在");
        }
        // 查询专辑属性值列表
        List<AlbumAttributeValue> attributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));
        // 设置专辑属性值列表到专辑信息对象中
        albumInfo.setAlbumAttributeValueVoList(attributeValues);
        // 返回专辑信息对象
        return albumInfo;
    }


    /**
     * 更新专辑信息的方法。
     * 该方法用于更新指定专辑的基本信息和标签信息。整个过程在一个事务中执行，确保数据的一致性。
     * 如果在过程中发生任何异常，将回滚所有更改。
     *
     * @param albumInfoVo 专辑信息视图对象，包含要更新的专辑信息（如名称、描述、分类等）
     * @param userId 用户ID，标识操作者，用于权限验证或日志记录（当前代码未使用）
     * @param albumId 专辑ID，用于唯一标识要更新的专辑
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateAlbumInfo(AlbumInfoVo albumInfoVo, Long userId, Long albumId) {

        //先删除缓存
        // 先删除缓存
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        redisTemplate.delete(cacheKey);


        // 1. 修改专辑基本信息（最新的分类）

        // 1.1 查询该专辑的老数据
        // 根据专辑ID从数据库中查询现有专辑信息，如果专辑不存在则抛出异常
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        if (null == albumInfo) {
            throw new GuiguException(201, "专辑不存在");
        }

        // 1.2 将新信息复制到旧对象中，以便更新
        // 使用BeanUtils.copyProperties方法将albumInfoVo中的属性复制到albumInfo中，
        // 如果albumInfoVo中某些属性为空，则保留albumInfo中原有的值
        BeanUtils.copyProperties(albumInfoVo, albumInfo);

        // 1.3 根据付费类型更新专辑的价格和折扣信息
        // 如果专辑是免费的，则设置相关字段为默认值；否则设置试听曲目数量为5
        if (albumInfoVo.getPayType().equals(SystemConstant.ALBUM_PAY_TYPE_FREE)) {
            albumInfo.setTracksForFree(0);
            albumInfo.setDiscount(new BigDecimal("-1.00"));  // 设置折扣为无效值
            albumInfo.setVipDiscount(new BigDecimal("-1.00"));  // 设置VIP折扣为无效值
            albumInfo.setPrice(new BigDecimal("0.00"));  // 设置价格为0
        } else {
            albumInfo.setTracksForFree(5);  // 设置试听曲目数量为5
        }

        // 1.4 更新数据库中的专辑信息
        // 将更新后的专辑信息保存到数据库中
        albumInfoMapper.updateById(albumInfo);

        // 2. 修改专辑的标签信息

        // 2.1 删除这个专辑的老标签信息
        // 删除与该专辑关联的所有旧标签信息，确保标签信息是最新的
        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));

        // 2.2 新增这个专辑的新标签
        // 获取新的标签信息列表，并将其转换为实体对象列表
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();

        List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueVoList.stream().map(albumAttributeValueVo -> {
            AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
            albumAttributeValue.setAlbumId(albumId);  // 设置专辑ID
            albumAttributeValue.setAttributeId(albumAttributeValueVo.getAttributeId());  // 设置标签属性ID
            albumAttributeValue.setValueId(albumAttributeValueVo.getValueId());  // 设置标签值ID
            return albumAttributeValue;
        }).collect(Collectors.toList());

        // 2.3 如果有新的标签信息，则批量保存
        // 如果新的标签信息列表不为空，则批量插入新的标签信息到数据库中
        if (!CollectionUtils.isEmpty(albumAttributeValues)) {
            albumAttrAndValueService.saveBatch(albumAttributeValues);
        }
    }


    /**
     * 删除专辑信息
     * 本方法执行专辑信息的全面删除操作，包括专辑的基本信息、统计信息和标签信息
     * 删除操作在事务中执行，确保数据的一致性如果专辑下存在声音声音，则抛出异常，防止错误删除
     *
     * @param albumId 专辑ID，用于标识要删除的专辑
     * @throws GuiguException 如果专辑下存在声音声音，则抛出此异常，异常代码为201，提示"该专辑下存在声音，请勿删除"
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeAlbumInfo(Long albumId) {

        // 1.删除专辑的基本信息
        Long count = trackInfoMapper.selectCount(new LambdaQueryWrapper<TrackInfo>().eq(TrackInfo::getAlbumId, albumId));
        if (count > 0) {
            throw new GuiguException(201, "该专辑下存在声音，请勿删除");
        }
        albumInfoMapper.deleteById(albumId);

        // 2.删除专辑统计信息
        albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>().eq(AlbumStat::getAlbumId, albumId));

        // 3.删除专辑的标签信息
        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));

    }


    /**
     * 根据用户ID查询该用户下所有的专辑图片列表
     *
     * 此方法通过MyBatis Plus的LambdaQueryWrapper构建查询条件，筛选出特定用户ID下未删除的专辑图片信息
     * 选择使用Lambda表达式是为了提高代码的可读性和安全性，避免直接使用字符串可能导致的错误
     *
     * @param userId 用户ID，用于查询该用户下的所有专辑图片
     * @return 返回用户所有的专辑图片列表，如果用户没有专辑图片，则返回空列表
     */
    @Override
    public List<AlbumInfo> findUserAllAlbumList(Long userId) {
        // 创建LambdaQueryWrapper实例，用于构建查询条件
        LambdaQueryWrapper<AlbumInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件，筛选出与传入用户ID相匹配的专辑图片
        lambdaQueryWrapper.eq(AlbumInfo::getUserId, userId);
        // 添加查询条件，确保只查询未删除的专辑图片
        lambdaQueryWrapper.eq(AlbumInfo::getIsDeleted, 0);
        // 执行查询并返回结果
        return albumInfoMapper.selectList(lambdaQueryWrapper);
    }

    @Override
    public BaseCategoryView getAlbumInfoCategory(Long albumId) {
        return baseCategory3Mapper.getAlbumInfoCategory(albumId);
    }

    @Override
    public AlbumStatVo getAlbumInfoStat(Long albumId) {
        return albumInfoMapper.getAlbumInfoStat(albumId);
    }

    /**
     * 获取所有专辑信息ID列表
     * 此方法通过查询数据库中的所有专辑信息，并返回一个包含所有专辑ID的列表
     *
     * @return 包含所有专辑ID的列表，如果数据库中没有专辑信息，则返回空列表
     */
    @Override
    public ArrayList<Long> getAllAlbumInfoIds() {
        ArrayList<Long> albumInfoIds = new ArrayList<>();
        albumInfoMapper.selectList(null)
                .forEach(albumInfo -> {
                    albumInfoIds.add(albumInfo.getId());
                });
        return albumInfoIds;
    }

    @Override
    public List<TrackListVo> getTrackInfoList(List<Long> collectTrackIds) {
        if (!CollectionUtils.isEmpty(collectTrackIds)) {
            List<TrackListVo> trackListVos = new ArrayList<>();
            List<TrackInfo> trackInfos = trackInfoMapper.selectBatchIds(collectTrackIds);
            trackInfos.forEach(trackInfo -> {
                TrackListVo trackListVo = new TrackListVo();
                trackListVo.setAlbumId(trackInfo.getAlbumId());
                trackListVo.setTrackId(trackInfo.getId());
                trackListVo.setTrackTitle(trackInfo.getTrackTitle());
                trackListVo.setCoverUrl(albumInfoMapper.selectById(trackInfo.getAlbumId()).getCoverUrl());
                trackListVo.setMediaDuration(trackInfo.getMediaDuration());
                trackListVo.setAlbumTitle(albumInfoMapper.selectById(trackInfo.getAlbumId()).getAlbumTitle());
                trackListVos.add(trackListVo);
            });
            return trackListVos;
        }
        return new ArrayList<>();
    }


}
